<script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import hljs from 'highlight.js/lib/core';

interface VirtualLogViewerProps {
    /** 日志内容数组，每个元素是一行日志 */
    logs: string[];
    /** 搜索关键字 */
    searchKeyword?: string;
    /** 每行的高度（像素） */
    itemHeight?: number;
    /** 容器高度 */
    height?: number;
    /** 语言类型，用于语法高亮 */
    language?: string;
    /** 是否自动滚动到底部 */
    autoScroll?: boolean;
}

const props = withDefaults(defineProps<VirtualLogViewerProps>(), {
    itemHeight: 20,
    height: 400,
    language: 'plaintext',
    autoScroll: true
});

const emit = defineEmits<{
    scroll: [scrollTop: number];
}>();

// 容器引用
const containerRef = ref<HTMLDivElement>();
const scrollRef = ref<HTMLDivElement>();

// 虚拟滚动状态
const scrollTop = ref(0);
const containerHeight = ref(props.height);

// 计算可见区域
const visibleCount = computed(() => Math.ceil(containerHeight.value / props.itemHeight) + 2);
const totalHeight = computed(() => props.logs.length * props.itemHeight);

const startIndex = computed(() => {
    const index = Math.floor(scrollTop.value / props.itemHeight);
    return Math.max(0, index - 1);
});

const endIndex = computed(() => {
    return Math.min(props.logs.length, startIndex.value + visibleCount.value);
});

const visibleLogs = computed(() => {
    return props.logs.slice(startIndex.value, endIndex.value).map((log, index) => ({
        content: log,
        index: startIndex.value + index,
        top: (startIndex.value + index) * props.itemHeight
    }));
});

// 搜索高亮处理
const highlightedLogs = computed(() => {
    return visibleLogs.value.map((log) => {
        let content = log.content;

        // 如果有搜索关键词，先进行搜索高亮
        if (props.searchKeyword) {
            const keyword = props.searchKeyword.toLowerCase();
            if (content.toLowerCase().includes(keyword)) {
                const regex = new RegExp(`(${escapeRegExp(props.searchKeyword)})`, 'gi');
                content = content.replace(
                    regex,
                    '###SEARCH_HIGHLIGHT_START###$1###SEARCH_HIGHLIGHT_END###'
                );
            }
        }

        // 再进行语法高亮
        let highlighted = highlightCode(content);

        // 最后将占位符替换为实际的高亮标签
        if (props.searchKeyword) {
            highlighted = highlighted.replace(
                /###SEARCH_HIGHLIGHT_START###(.*?)###SEARCH_HIGHLIGHT_END###/g,
                '<mark class="search-highlight">$1</mark>'
            );
        }

        return {
            ...log,
            highlighted
        };
    });
});

// 语法高亮函数
function highlightCode(code: string): string {
    try {
        if (props.language && props.language !== 'plaintext') {
            return hljs.highlight(code, { language: props.language }).value;
        }
        return hljs.highlightAuto(code).value;
    } catch {
        return code;
    }
}

// 转义正则表达式特殊字符
function escapeRegExp(string: string): string {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// 滚动处理
function handleScroll(event: Event) {
    const target = event.target as HTMLDivElement;
    scrollTop.value = target.scrollTop;
    emit('scroll', target.scrollTop);
}

// 滚动到指定位置
function scrollTo(options: { top?: number; behavior?: ScrollBehavior }) {
    if (scrollRef.value) {
        scrollRef.value.scrollTo({
            top: options.top || 0,
            behavior: options.behavior || 'smooth'
        });
    }
}

// 滚动到底部
function scrollToBottom() {
    nextTick(() => {
        if (scrollRef.value) {
            scrollRef.value.scrollTop = totalHeight.value;
        }
    });
}

// 滚动到指定行
function scrollToLine(lineIndex: number) {
    const top = lineIndex * props.itemHeight;
    scrollTo({ top, behavior: 'smooth' });
}

// 查找匹配的行
function findMatchingLines(keyword: string): number[] {
    if (!keyword) return [];

    const lowerKeyword = keyword.toLowerCase();
    const matchingLines: number[] = [];

    props.logs.forEach((log, index) => {
        if (log.toLowerCase().includes(lowerKeyword)) {
            matchingLines.push(index);
        }
    });

    return matchingLines;
}

// 监听日志变化，自动滚动到底部
watch(
    () => props.logs.length,
    (newLength, oldLength) => {
        if (props.autoScroll && newLength > oldLength) {
            scrollToBottom();
        }
    }
);

// 更新容器高度
function updateContainerHeight() {
    if (containerRef.value) {
        containerHeight.value = containerRef.value.clientHeight;
    }
}

// 暴露方法给父组件
defineExpose({
    scrollTo,
    scrollToBottom,
    scrollToLine,
    findMatchingLines
});

onMounted(() => {
    updateContainerHeight();
    window.addEventListener('resize', updateContainerHeight);

    if (props.autoScroll) {
        scrollToBottom();
    }
});

onUnmounted(() => {
    window.removeEventListener('resize', updateContainerHeight);
});
</script>

<template>
    <div ref="containerRef" class="virtual-log-viewer" :style="{ height: height + 'px' }">
        <div ref="scrollRef" class="scroll-container" @scroll="handleScroll">
            <div class="virtual-content" :style="{ height: totalHeight + 'px' }">
                <div
                    v-for="log in highlightedLogs"
                    :key="log.index"
                    class="log-line"
                    :style="{
                        position: 'absolute',
                        top: log.top + 'px',
                        height: itemHeight + 'px',
                        width: '100%'
                    }"
                >
                    <span class="line-number">{{ log.index + 1 }}</span>
                    <span class="line-content" v-html="log.highlighted" />
                </div>
            </div>
        </div>
    </div>
</template>

<style scoped>
.virtual-log-viewer {
    position: relative;
    background: var(--n-color);
    font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
    font-size: 13px;
    line-height: 1.5;
    height: 100%;
}

.scroll-container {
    height: 100%;
    overflow: auto;
    position: relative;
}

.virtual-content {
    position: relative;
    width: max-content;
    min-width: 100%;
}

.log-line {
    display: flex;
    align-items: center;
    padding: 0 12px;
    border-bottom: 1px solid var(--n-divider-color);
    white-space: nowrap;
    overflow: visible;
    min-width: 100%;
}

.log-line:hover {
    background: var(--n-color-hover);
}

.line-number {
    display: inline-block;
    width: 60px;
    text-align: right;
    color: var(--n-text-color-disabled);
    margin-right: 12px;
    flex-shrink: 0;
    user-select: none;
}

.line-content {
    flex: 1;
    overflow: visible;
    white-space: nowrap;
    min-width: 0;
}

.line-content :deep(.search-highlight) {
    background-color: #ffeb3b !important;
    color: #000 !important;
    padding: 0 2px;
    border-radius: 2px;
    font-weight: bold !important;
    font-style: normal !important;
}

/* 语法高亮样式 */
.line-content :deep(.hljs-keyword) {
    color: #569cd6;
    font-weight: bold;
}

.line-content :deep(.hljs-string) {
    color: #ce9178;
}

.line-content :deep(.hljs-number) {
    color: #b5cea8;
}

.line-content :deep(.hljs-comment) {
    color: #6a9955;
    font-style: normal;
}

.line-content :deep(.hljs-function) {
    color: #dcdcaa;
}

.line-content :deep(.hljs-variable) {
    color: #9cdcfe;
}

.line-content :deep(.hljs-type) {
    color: #4ec9b0;
}

.line-content :deep(.hljs-built_in) {
    color: #4fc1ff;
}

.line-content :deep(.hljs-literal) {
    color: #569cd6;
}

.line-content :deep(.hljs-title) {
    color: #dcdcaa;
}

.line-content :deep(.hljs-attr) {
    color: #92c5f8;
}

.line-content :deep(.hljs-meta) {
    color: #569cd6;
}

/* 滚动条样式 */
.scroll-container::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

.scroll-container::-webkit-scrollbar-track {
    background: var(--n-scrollbar-color);
    border-radius: 4px;
}

.scroll-container::-webkit-scrollbar-thumb {
    background: var(--n-scrollbar-color-hover);
    border-radius: 4px;
}

.scroll-container::-webkit-scrollbar-thumb:hover {
    background: var(--n-text-color-disabled);
}
</style>
